#include "arch_control.h"

ArchControl::ArchControl(std::string& nombre, unsigned int tam_bloque):Archivo(nombre, tam_bloque,0){
}

ArchControl::~ArchControl(){
	//Si el archivo de control esta abierto, lo cerramos
	this->cerrar();	
}

void ArchControl::abrir() throw(ExcepAbriendoArch){
	//Si el archivo esta abierto, arrojo excep
	if (this->fd != -1)
		throw ExcepAbriendoArch("Error - El archivo: " + this->get_nombre() + " ya estaba abierto.");
	else {
		std::string dest = "./DATA/" + this->get_nombre();
		this->fd = ManejadorArchivos::get_instance()->abrir_archivo(dest);
		if ( this->fd == -1)
			throw ExcepAbriendoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
		this->cant_bloques = get_cant_bloques_header();
	}
}
	
void ArchControl::crear() throw (ExcepCreandoArch) {
	std::string dest = "./DATA/" + this->get_nombre();
	
	this->fd = ManejadorArchivos::get_instance()->crear_archivo(dest);
	if ( this->fd == -1 )
		throw ExcepCreandoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
	this->crear_bloque(); //Creo el 1er bloque
	set_cant_bloques_header(1);
}
	
void ArchControl::cerrar() {
	if (this->fd != -1) {
		(Debug::get_instance() )->mostrar_mensaje("> Se cierra el archivo: " + this->get_nombre());
		set_cant_bloques_header(this->cant_bloques);
		this->set_cambio();
		this->avisar_observadores(NULL);
		ManejadorArchivos::get_instance()->cerrar_archivo(this->fd);
		this->fd = -1;
	}
}

void ArchControl::append(unsigned int valor) throw(ExcepArchCerrado, ExcepEscribirBloque,ExcepLeerBloque){
	Bloque * bloque = Buffer::get_instance()->get_bloque(*this,this->cant_bloques - 1);
	if (bloque->get_espacio_libre() < TAM_REG) {
		delete (bloque);
		this->crear_bloque();
		bloque = Buffer::get_instance()->get_bloque(*this,this->cant_bloques - 1);
	}
	unsigned int pos = bloque->get_tope();
	bloque->escribir((char*) &valor, pos, TAM_REG);
	Buffer::get_instance()->guardar_bloque(*this, bloque);
	delete (bloque);
}

void ArchControl::eliminar(){
	this->cerrar();
	std::string mi_ruta = "./DATA/" + this->get_nombre();
	remove(mi_ruta.c_str()); 
}

TiposArchivo ArchControl::get_tipo_archivo(){
	return CONTROL;
}

unsigned int ArchControl::get_valor(unsigned int num_slot){
	unsigned int resultado = 0;
	unsigned int nbloque = slot_to_nbloque(num_slot);
	unsigned int offset = slot_to_despl(num_slot);
	Bloque * bloque = Buffer::get_instance()->get_bloque(*this,nbloque);

	bloque->leer((char*) &resultado, offset, TAM_REG);
	
	delete bloque;
	
	return resultado;
}

void ArchControl::set_valor(unsigned int num_slot, unsigned int valor){
	unsigned int nbloque = slot_to_nbloque(num_slot);
	unsigned int offset = slot_to_despl(num_slot);

	Bloque * bloque = Buffer::get_instance()->get_bloque(*this,nbloque);
	bloque->escribir((char*) &valor, offset, TAM_REG);

	Buffer::get_instance()->guardar_bloque(*this, bloque);
	delete (bloque);
}

void ArchControl::borrar_ultimo(){
	Bloque* bloque = Buffer::get_instance()->get_bloque(*this,cant_bloques - 1);
	int pos_fin = bloque->get_tope();
	int pos_init = pos_fin - TAM_REG;
	bool es_bq_0 = (bloque->get_num_bloque() == 0);
	if ( ((!es_bq_0) && (pos_init >= 0)) || ((es_bq_0) && (pos_init >= TAM_REG)) ) {
		bloque->comprimir(pos_init, TAM_REG);	
		if (bloque->get_tope() == 0) {
			unsigned int pos_trunc = (cant_bloques - 1) * this->get_tam_bloque();	
			ManejadorArchivos::get_instance()->truncar_arch(this->get_fd(), pos_trunc);
			cant_bloques--;
			Buffer::get_instance()->invalidar_bloque (*this, cant_bloques);
		} else
			Buffer::get_instance()->guardar_bloque(*this, bloque);
	}
	delete(bloque);	
}

void ArchControl::set_cant_bloques_header(unsigned int cant){
	Bloque * bloque = Buffer::get_instance()->get_bloque(*this, 0);
	bloque->escribir((char*) &cant,0, TAM_REG);
	Buffer::get_instance()->guardar_bloque(*this, bloque);
	delete bloque;
}
		
unsigned int ArchControl::get_cant_bloques_header(){
	unsigned int cant;
	Bloque * bloque = Buffer::get_instance()->get_bloque(*this, 0);
	bloque->leer((char*) &cant,0, TAM_REG);
	delete bloque;
	return cant;
}

void ArchControl::crear_bloque() throw(ExcepEscribirBloque){
	
	Bloque * bloque = new Bloque(this->cant_bloques, this->get_tam_bloque());
	
	Buffer::get_instance()->guardar_bloque(*this, bloque);
	delete bloque;
	
	this->cant_bloques++;
}

unsigned int ArchControl::get_espacio_libre() throw(ExcepArchCerrado, ExcepLeerBloque){
	
	unsigned int espacio_libre_total = 0;
	Bloque * bloque;
	
	for(unsigned int num_bloque = 0; num_bloque < this->cant_bloques; num_bloque++){
		bloque = Buffer::get_instance()->get_bloque(*this,num_bloque);
		espacio_libre_total += bloque->get_espacio_libre();
		delete bloque;
	}
		
	return espacio_libre_total;
}

unsigned int ArchControl::get_cantidad_elementos(){
	unsigned int cant_reg_bq = this->get_tam_bloque() / TAM_REG - 1;
	unsigned int cant_reg_bq_0 = cant_reg_bq - 1;
	unsigned int cant_elementos = 0;
	
	
	//obtengo los del ultimo elemento
	Bloque* b = Buffer::get_instance()->get_bloque(*this, this->cant_bloques - 1);
	cant_elementos = b->get_tope() / TAM_REG;
	
	if(this->cant_bloques > 1){
		//ahora, si tengo mas que dos, agego los del 0
		cant_elementos += cant_reg_bq_0;
		//agrego los que estan antes del ultimo que estan llenos.
		//Tener en cuenta que si esta solamente el bq0 y uno mas parcialmente
		//lleno, esta cuenta da 0
		cant_elementos += (this->cant_bloques - 2) * cant_reg_bq;
	}
	else{
		//resto porque me cuenta como uno el tag de cant de bloques
		cant_elementos -= 1;
	}
	
	delete b;
	
	return cant_elementos;
	
	
}

unsigned int ArchControl::slot_to_nbloque(unsigned int slot){
	unsigned int resultado;
	unsigned int cant_reg_bq = this->get_tam_bloque() / TAM_REG - 1;
	unsigned int cant_reg_bq_0 = cant_reg_bq - 1;
	if (slot < cant_reg_bq_0 )
		resultado = 0;
	else
		resultado = ((slot - cant_reg_bq_0) / cant_reg_bq) + 1;
	
	return resultado;
}

unsigned int ArchControl::slot_to_despl(unsigned int slot){
	unsigned int resultado;
	unsigned int cant_reg_bq = this->get_tam_bloque() / TAM_REG - 1;
	unsigned int cant_reg_bq_0 = cant_reg_bq - 1;
	if (slot < cant_reg_bq_0 )
		resultado = (slot + 1) * TAM_REG;
	else
		resultado = TAM_REG * ((slot - cant_reg_bq_0) % cant_reg_bq);
	
	return resultado;
}

unsigned int ArchControl::nbloque_to_slot(unsigned int cont_bloques, unsigned int pos_reg){
	unsigned int resultado = 0;
	unsigned int cant_reg_bq = this->get_tam_bloque() / TAM_REG - 1;
	unsigned int cant_reg_bq_0 = cant_reg_bq - 1;
	if (cont_bloques == 0)
		resultado = (pos_reg / TAM_REG) - 1;
	else
		resultado = cant_reg_bq_0 + (cont_bloques - 1) * cant_reg_bq + pos_reg / TAM_REG;
	return resultado;
}
